all files / src/ gregorian.ts

90.48% Statements 76/84
81.25% Branches 26/32
100% Functions 11/11
90.12% Lines 73/81
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 1531× 1× 1× 1×             1×   1× 1×   1× 1×   1× 1×   1×   1×   1× 1× 9509×     771769× 771768× 771768×   771768×     1× 159124× 159124×   159124× 159124×   159124× 159124×   159124× 159124×   159124× 159124×       159124× 159018×     159124×   159124× 159124× 159124× 25395× 133729× 32542×   101187×     159124× 159124×   159124×     1× 1×     1× 159124×     1× 318240×             1× 477372× 210786× 210786×     477372× 477372× 477372× 477372× 477372×   477372×     1× 477372× 477372×                                       1× 159124×           1× 771768× 771768× 59736×   712032×     771768×       771768×   1×  
import { isLeapYear } from "./common";
import { IHistoricalDate } from './historical-date';
import { InvalidDateException } from './invalid-date-exception';
import { JulianDate } from "./julian";
 
// Licensed under the MIT license:
// http://opensource.org/licenses/MIT
// Copyright (c) 2016, fitnr <fitnr@fakeisthenewreal>
// based on https://github.com/fitnr/convertdate/blob/master/convertdate/gregorian.py
 
const EPOCH = 1721425.5;
 
const INTERCALATION_CYCLE_YEARS = 400;
const INTERCALATION_CYCLE_DAYS = 146097;
 
const LEAP_SUPPRESSION_YEARS = 100;
const LEAP_SUPPRESSION_DAYS = 36524;
 
const LEAP_CYCLE_YEARS = 4;
const LEAP_CYCLE_DAYS = 1461;
 
const YEAR_DAYS = 365;
 
const HAVE_30_DAYS = [4, 6, 9, 11];
 
export class GregorianDate extends IHistoricalDate<'gregorian'> {
    public get isLeapYear() {
        return isLeapYear(this.year, this.calendar);
    }
 
    constructor(public readonly year: number,
        public readonly month: number,
        public readonly day: number) {
        super('gregorian');
        this.assertLegalDate(year, month, day);
    }
 
    static fromJulianDays(jd: number) {
        let wjd = Math.floor(jd - 0.5) + 0.5;
        let depoch = wjd - EPOCH;
 
        let quadricent = Math.floor(depoch / INTERCALATION_CYCLE_DAYS);
        let dqc = depoch % INTERCALATION_CYCLE_DAYS;
 
        let cent = Math.floor(dqc / LEAP_SUPPRESSION_DAYS);
        let dcent = dqc % LEAP_SUPPRESSION_DAYS;
 
        let quad = Math.floor(dcent / LEAP_CYCLE_DAYS);
        let dquad = dcent % LEAP_CYCLE_DAYS;
 
        let yindex = Math.floor(dquad / YEAR_DAYS);
        let year = quadricent * INTERCALATION_CYCLE_YEARS +
            cent * LEAP_SUPPRESSION_YEARS +
            quad * LEAP_CYCLE_YEARS + yindex;
 
        if (cent != 4 && yindex != 4) {
            year += 1;
        }
 
        let yearDay = wjd - GregorianDate.toJulianDays(year, 1, 1);
 
        let leap = isLeapYear(year);
        let leapAdj: number;
        if (yearDay < (58 + (leap ? 1 : 0))) {
            leapAdj = 0;
        } else if (leap) {
            leapAdj = 1;
        } else {
            leapAdj = 2;
        }
 
        let month = Math.floor((((yearDay + leapAdj) * 12) + 373) / 367);
        let day = Math.trunc(wjd - GregorianDate.toJulianDays(year, month, 1)) + 1;
 
        return new GregorianDate(year, month, day);
    }
 
    public toGregorian() {
        return this;
    }
 
    public toJulian() {
        return JulianDate.fromJulianDays(this.toJulianDays());
    }
 
    public toString() {
        return `${this.year}-${this.month}-${this.day}`;
    }
 
    /**
     * Gregorian to Julian Day Count for years between 1801-2099
     * @see http://quasar.as.utexas.edu/BillInfo/JulianDatesG.html
     */
    private static toJulianDays1801(year: number, month: number, day: number) {
        if (month <= 2) {
            year = year - 1;
            month = month + 12;
        }
 
        let a = Math.floor(year / 100);
        let b = Math.floor(a / 4);
        let c = 2 - a + b;
        let e = Math.floor(365.25 * (year + 4716));
        let f = Math.floor(30.6001 * (month + 1));
 
        return c + day + e + f - 1524.5
    }
 
    private static toJulianDays(year: number, month: number, day: number) {
        Eif (year >= 1801 || year <= 2099) {
            return this.toJulianDays1801(year, month, day);
        }
 
        let leapAdj;
 
        if (month <= 2) {
            leapAdj = 0
        } else if (isLeapYear(year)) {
            leapAdj = -1
        } else {
            leapAdj = -2
        }
 
        return EPOCH - 1 + (YEAR_DAYS * (year - 1)) +
            Math.floor((year - 1) / LEAP_CYCLE_YEARS) +
            (-Math.floor((year - 1) / LEAP_SUPPRESSION_YEARS)) +
            Math.floor((year - 1) / INTERCALATION_CYCLE_YEARS) +
            Math.floor((((367 * month) - 362) / 12) + leapAdj + day);
    }
 
    private toJulianDays() {
        return GregorianDate.toJulianDays(this.year, this.month, this.day);
    }
 
    /**
     * Check if this is a legal date in the Gregorian calendar
     */
    private assertLegalDate(year: number, month: number, day: number) {
        let daysInMonth: number;
        if (month == 2) {
            daysInMonth = isLeapYear(year) ? 29 : 28;
        } else {
            daysInMonth = HAVE_30_DAYS.indexOf(month) >= 0 ? 30 : 31;
        }
 
        Iif (day < 0 || day > daysInMonth) {
            throw new InvalidDateException(`Month ${month} doesn't have a day ${day}`);
        }
 
        return true;
    }
}